package belastungstest.mobapp.fhfl.mobappbelastungstest;

import android.content.Context;
import android.os.AsyncTask;
import android.provider.ContactsContract;
import android.util.Log;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EventListener;
import java.util.List;
import android.os.Handler;
import android.widget.TextView;

/**
 * Created by illu on 01.07.2014.
 */

public class Transmitter {
    private InetAddress serverIP = null;
    private DatagramSocket datagramSocket = null;
    private int serverPort = 0;
    private Boolean isRunning = false;

    private int factoryTimes = 0;
    private ContentFactory factory;
    private ContentFactory factoryLeftOver;

    private synchronized DatagramSocket getDatagramSocket() {
        return this.datagramSocket;
    }

    private synchronized void setDatagramSocket(DatagramSocket datagramSocket) {
        this.datagramSocket = datagramSocket;
    }

    //region Threads

    private ReciverThread reciver;
    private SenderThread sender;

    private class ReciverThread extends Thread {
        private int lastPacketNumber = 0;
        byte[] buffer;
        byte[] bufferLeftOver;

        DatagramPacket packet;
        DatagramPacket packetLeftOver;

        int lostPackage = 0;

        public ReciverThread() {
            buffer = new byte[factory.getSendSize()];
            packet = new DatagramPacket(buffer, buffer.length);

            bufferLeftOver = new byte[factoryLeftOver.getSendSize()];
            packetLeftOver = new DatagramPacket(bufferLeftOver, bufferLeftOver.length);

        }


        @Override
        public void run() {
            do//if(isRunning)
            {
                try {
                    int times = factoryTimes;

                    while (times-- >= 0) {
                        byte[] localBuffer;
                        ContentFactory localFactory;

                        if (times == -1) {
                            datagramSocket.receive(packetLeftOver);
                            localBuffer = bufferLeftOver;
                            localFactory = factoryLeftOver;
                        } else {
                            datagramSocket.receive(packet);
                            localBuffer = buffer;
                            localFactory = factory;
                        }
                        int packetNumber = factory.getPacketNumberFromPacket(localBuffer);

                        int lostPackets = 0;
                        if (packetNumber - 1 > lastPacketNumber) {
                            lostPackets = packetNumber - 1 - lastPacketNumber;
                        } else if (packetNumber - 1 < lastPacketNumber) {
                            break;
                        }
                        lastPacketNumber = packetNumber;

                        int latency = (int) ((double) (System.currentTimeMillis() - localFactory.getTimeFromPacket(localBuffer)) / (double) 2);

                        float packetIntegrety = localFactory.getPacketPercentage(localBuffer);
                        int packetSize = (int) (localFactory.getPacketSize() * packetIntegrety / 100.0f);

                        raiseEvent(new ReciveEvent(ReciveEventTypes.newData, latency, packetSize, packetNumber, lostPackets, packetIntegrety));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    Log.d("recive-lost:", "lost lastPacketNumber:" + lastPacketNumber);
                }


            } while (isRunning);
        }
    }

    private class SenderThread extends Thread {
        DatagramPacket packet;
        DatagramPacket packetLeftOver;

        private int sendIntervall = 100;

        public SenderThread(int sendIntervall) {
            this.sendIntervall = sendIntervall;
            this.packet = new DatagramPacket(factory.setPackage(), factory.getSendSize(), serverIP, serverPort);
            this.packetLeftOver = new DatagramPacket(factoryLeftOver.setPackage(), factoryLeftOver.getSendSize(), serverIP, serverPort);


        }

        @Override
        public void run() {

            long startTime = System.currentTimeMillis();
            do //if(isRunning)
            {
                try {
                    //Thread.sleep(this.sendIntervall, 0);
                    //sender = new SenderThread(sendIntervall);
                    //sender.start();
                    int times = factoryTimes;

                    while (times-- > 0) {
                        factory.incrementPacketNumber();
                        factoryLeftOver.incrementPacketNumber();
                        packet.setData(factory.setPackage());

                        datagramSocket.send(packet);
                    }

                    factory.incrementPacketNumber();
                    factoryLeftOver.incrementPacketNumber();
                    packetLeftOver.setData(factoryLeftOver.setPackage());
                    datagramSocket.send(packetLeftOver);

                    // long nower = System.currentTimeMillis();
                    Thread.sleep(this.sendIntervall);//-(startTime-nower));
                    //Thread.sleep(1);
                    //startTime = nower;
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.d("send:", "IOException");
                } catch (InterruptedException e) {
                    Log.d("send:", "InterruptedException");
                }

            } while (isRunning);

        }
    }

    //endregion

    //region ReciveEventListener

    private List<ReciveEventListener> reciveListeners = new ArrayList<ReciveEventListener>();

    private synchronized void raiseEvent(ReciveEvent reciveEvent) {
        for (ReciveEventListener object : reciveListeners) {
            if (object != null)
                object.onReciveEvent(reciveEvent);
        }
    }

    enum ReciveEventTypes {
        newData
    }

    public class ReciveEvent {
        ReciveEventTypes type = null;
        int latency = 0;
        int packetSize = 0;
        int packetNumber = 0;
        int lostPackets = 0;
        float packetIntegrety = 100;

        public ReciveEvent(ReciveEventTypes type, int latency, int packetSize, int packetNumber, int lostPackets, float packetIntegrety) {
            this.type = type;
            this.latency = latency;
            this.packetSize = packetSize;
            this.packetNumber = packetNumber;
            this.lostPackets = lostPackets;
            this.packetIntegrety = packetIntegrety;
        }

    }

    public interface ReciveEventListener extends EventListener {
        public void onReciveEvent(ReciveEvent e);
    }

    public synchronized void addReciveEventListener(ReciveEventListener listener) {
        reciveListeners.add(listener);
    }

    public synchronized void removeReciveEventListener(ReciveEventListener listener) {
        reciveListeners.remove(listener);
    }
    //endregion

    public Transmitter(InetAddress serverIP, int serverPort, int packetSize, int sendIntervall, Boolean loop, ReciveEventListener reciveEventListener) throws IOException, SecurityException, IllegalArgumentException {
        Log.d("Transmitter:", "serverIP:" + serverIP.getHostAddress() + "  serverPort:" + serverPort + "  packetSize:" + packetSize + "   sendIntervall:" + sendIntervall);

        this.isRunning = loop;
        this.serverPort = serverPort;
        this.serverIP = serverIP;

        //if( packetSize/MagicNumbers.MAX_UDP_PACKET_SIZE > 0 )
        //{
        this.factoryLeftOver = new ContentFactory((packetSize % MagicNumbers.MAX_UDP_PACKET_SIZE) + MagicNumbers.DATE_LENGTH + MagicNumbers.PACKET_NUMBER_LENGTH);
        this.factory = new ContentFactory(MagicNumbers.MAX_UDP_PACKET_SIZE);
        this.factoryTimes = packetSize / MagicNumbers.MAX_UDP_PACKET_SIZE;
        /*}
        else
        {
            Log.d("Itegrety:", packetSize+" 1 ");
            this.factory = new ContentFactory(MagicNumbers.DATE_LENGTH+MagicNumbers.PACKET_NUMBER_LENGTH);
            this.factoryLeftOver = new ContentFactory(packetSize+MagicNumbers.DATE_LENGTH+MagicNumbers.PACKET_NUMBER_LENGTH);
            this.factoryTimes = 0;
        }*/

        this.datagramSocket = new DatagramSocket();

        this.sender = new SenderThread(sendIntervall);
        this.reciver = new ReciverThread();
        this.reciver.setPriority(Thread.MAX_PRIORITY - 2);
        this.sender.setPriority(Thread.MAX_PRIORITY - 1);

        this.addReciveEventListener(reciveEventListener);

        this.reciver.start();
        this.sender.start();
    }


    public Transmitter dispose() {
        reciveListeners.clear();
        isRunning = false;
        datagramSocket.close();
        return null;
    }

}